package projet.metier;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
import java.util.Map.Entry;
import projet.metier.GraphGenerator;

import edu.uci.ics.jung.graph.Graph;

/**
 * Location class. Implements method for get location of event, parkings and parcs.
 * @author Ambroise
 */
public class Location {
	
	// If the parking's number is 2 then the program will be search the very bests results in the 2*nbMultiplicatorParking best results.
	// Si le nombre de parking à placer est 2 alors le programme va chercher dans les 4 premiers résultats au niveau de la proximité pour calculer les vrais meilleurs résultats avec la prise en compte de la centralité de degré. [Oui, c'est mieux en FR !]
	private int nbMultiplicatorParking = 3;
	

	/**
	 * Get locations's event possibles.
	 * 
	 * @param graph - Graph<String, Integer> - The graph.
	 * @param nbResults - int - Number's position to get.
	 * @return String[] - The keys's location possibles.
	 */
	private String[] GetLocationsEvent(Graph<String, Integer> graph, int nbResults){
		if(nbResults <= 0){
			String result[] =  {AlgorithmProximity.GetMaxProximityByGraph(graph, false)};
			return result;
		}else{
			String result[] = new String[nbResults];
			Boolean deleteActualValue = false;
			HashMap<String, Float> graphProximities = AlgorithmProximity.GetProximitysByGraph(graph);
			
			for(int i = 0; i < nbResults; i++){
				if(!deleteActualValue && i > 0){
					deleteActualValue = true;
				}
				
				result[i] = AlgorithmProximity.FoundMaxProximity(graphProximities, deleteActualValue);
				//System.out.println(result[i]);
			}
			
			return result;
		}		
	}
	
	/**
	 * Get locations's parking possibles.
	 * 
	 * @param graph - Graph<String, Integer> - The graph.
	 * @param nbResults - int - Number's position to get.
	 * 
	 * @return String[] - The keys's location possibles.
	 */
	@SuppressWarnings("unchecked")
	private String[] GetLocationsParking(Graph<String, Integer> graph, int nbResults){
		if(nbResults > 0){
			// Will store the keys by importance.
			String keysMinProximity[] = new String[nbResults];
			
			// Don't delete the first value.
			Boolean deleteActualValue = false;
			
			// Will store all graph's proximities.
			HashMap<String, Float> graphProximities = AlgorithmProximity.GetProximitysByGraph(graph);// Don't update this value.
			HashMap<String, Float> graphProximitiesTemp = graphProximities;// Can update/delete/insert values.
			
			// Will store all degrees centralities.
			HashMap<String, Integer> degreeCentrality= new HashMap<String, Integer>();
			
			for(int i = 0; i < nbResults; i++){
				// If the first value is already calculated.
				if(!deleteActualValue && i > 0){
					deleteActualValue = true;
				}
				
				// Search the min proximity.
				keysMinProximity[i] = AlgorithmProximity.FoundMinProximity(graphProximitiesTemp, deleteActualValue);
				// Get the degree centrality for this result.
				degreeCentrality.put(keysMinProximity[i], graph.degree(keysMinProximity[i]));
			}
			
			// Will store the number's vertex min, max and the average.
			Integer minDegree = null;
			Integer aveDegree = null;
			Integer maxDegree = null;
			
			for(int i = 0; i < nbResults; i++){
				// Initialize.
				if(minDegree == null){
					minDegree = degreeCentrality.get(keysMinProximity[i]);
					aveDegree = degreeCentrality.get(keysMinProximity[i]);
					maxDegree = degreeCentrality.get(keysMinProximity[i]);
				}
				
				// Check if the current value is smaller of minVertex.
				if(degreeCentrality.get(keysMinProximity[i]) < minDegree){
					minDegree = degreeCentrality.get(keysMinProximity[i]);
				}
				
				// Add to the average vertex.
				aveDegree += degreeCentrality.get(keysMinProximity[i]);
				
				// Check if the current value is greater of maxVertex.
				if(degreeCentrality.get(keysMinProximity[i]) > maxDegree){
					maxDegree = degreeCentrality.get(keysMinProximity[i]);
				}
			}
			
			// Divide by the number of edges.
			aveDegree = aveDegree/nbResults;
			
			// Calculate the ratio
			
			// Smoothing values.			
			Float valuesTotalCentrality[] = new Float[nbResults];
			Float ratioDegreeMinMax = ((new Float(minDegree)*new Float(maxDegree))+aveDegree)/2;
			
			for(int i = 0; i < nbResults; i++){
				Float ratioDegree = new Float(degreeCentrality.get(keysMinProximity[i]))*ratioDegreeMinMax;
				
				valuesTotalCentrality[i] = ratioDegree/AlgorithmProximity.GetProximityByNode(graph, keysMinProximity[i]);
			}
			
			// Create the final results.
			int nbResultToReturn = nbResults/nbMultiplicatorParking;
			HashMap<String, Float> HashMap = new HashMap<String, Float>();
			String bestKeys[] = new String[nbResultToReturn];
			
			// Convert double array to a HashMap.
			for(int i = 0; i < nbResults; i++){
				HashMap.put(keysMinProximity[i], valuesTotalCentrality[i]);
			}
			
			for(int i = 0; i < nbResultToReturn; i++){
				Object[] o = Location.GetMaxValue(HashMap);
				bestKeys[i] = (String) o[0];
				HashMap = (HashMap<String, Float>) o[1];
			}
			
			return bestKeys;
		}else{
			return null;
		}
	}
	
	/**
	 * Get the locations of parcs by graph and the number of location asked
	 * @param graph The graph
	 * @param nbLocationsParcs Number of locations asked
	 * @return Return the nodes with the best betweenness centrality
	 */
	private String[] GetLocationsParc(Graph<String, Integer> graph, int nbLocationsParcs){
		BetweennessCentrality centrality = new BetweennessCentrality();
		return centrality.getBetweennessNodes(graph, nbLocationsParcs);
	}
	
	/**
	 * Return object's array contains the key with the max value in the hashMap and the new HashMap with the max key deleted.
	 * 
	 * @param hashMap - HashMap<String, Float> - The hashMap.

	 * @return Object[] - [0]: The key of the max value.\n- [1]: The new hashMap with the key of the max value deleted.
	 */
	public static Object[] GetMaxValue(HashMap<String, Float> hashMap){
		String maxKey = null;
		Float maxValue = null;
		
		Set<Entry<String, Float>> entrySet = hashMap.entrySet();
		
		Iterator<Entry<String, Float>> it = entrySet.iterator();

		while (it.hasNext()) {
			Entry<String, Float> entry = it.next();
			
			// Initialize.
			if(maxKey == null){
				maxKey = entry.getKey();
				maxValue = entry.getValue();
			}
			
			// Get the max key/value.
			if(entry.getValue() > maxValue){
				maxKey = entry.getKey();
				maxValue = entry.getValue();
			}			
		}
		
		// Remove the max key.
		hashMap.remove(maxKey);
		
		Object[] result = {maxKey, hashMap};
		
		return result;
	}
	
	/**
	 * Calcul the locations of Event, Parkings and Parcs. Get one event, one parc and ten percent of vertex for the parkings.
	 * 
	 * @param graph - Graph<String, Integer> - The graph.
	 * @return String[][] - Array of locations's arrays.
	 */
	public String[][] CalculAllLocations(Graph<String, Integer> graph){
		return CalculAllLocations(graph, 1, graph.getVertexCount()/10, graph.getVertexCount()/5);
	}
	
	/**
	 * Calcul the locations of Event, Parkings and Parcs. Can get several location.
	 * 
	 * @param graph - Graph<String, Integer> - The graph.
	 * @param nbLocationsEvents - int - Number's position of event to get.
	 * @param nbLocationsParkings - int - Number's position of parkings to get.
	 * @param nbLocationsParcs - int - Number's position of parcs to get.
	 * @return String[][] - Array of locations's arrays.
	 */
	public String[][] CalculAllLocations(Graph<String, Integer> graph, int nbLocationsEvents, int nbLocationsParkings, int nbLocationsParcs){
		String[] locationsEvent = GetLocationsEvent(graph, nbLocationsEvents);
		String[] locationsParking = GetLocationsParking(graph, nbLocationsParkings*nbMultiplicatorParking);
		
		String[] locationsParc = GetLocationsParc(graph, nbLocationsParcs);
		
		String[][] locations = {locationsEvent, locationsParking, locationsParc};
		
		return locations;
	}
	
	/**
	 * Generate all graphics.
	 * @param selectedFile - File - The file .net.
	 */
	public void GenerateGraphicsWithLocations(File selectedFile){
//		long start = System.nanoTime();
		// Create GraphGenerator object.
		GraphGenerator graphGen = new GraphGenerator();
		graphGen.DeletePNGs();
		// Generate simple graphe, no color, no special point.
		Graph<String, Integer> G = graphGen.GenerateFromNet(selectedFile.getAbsolutePath(), "Graphe simple", "Graphe simple");
		
		// Get locations of Event, parkings and parcs.
		String[][] locations = CalculAllLocations(G);
		
		String[] locationsEvent = locations[0];
		String[] locationsParking = locations[1];
		String[] locationsParc = locations[2];
		
		// Check if the parc location is the same of the event location. If so, get the second choice of parc location. (If exists !)
		int indexLocationsParc = 0;
		if(locationsParc[0] == locationsEvent[0] && locationsParc.length > 1){
			indexLocationsParc++;
		}
		
		// Execute Dijkstra algorithm for get shortest paths on the graph from event location.
		HashMap<String, Object[]> hash= DijkstraAlgorithm.Dijkstra(G, locationsEvent[0]);
		graphGen.GenerateShortestPath(hash, G, "Graphe - Chemins les plus courts ["+locationsEvent[0]+"]", "Graphe - Chemins les plus courts ["+locationsEvent[0]+"]");

		// Generate graph with locations of event, parkings, parcs.
		graphGen.GeneratePlaces(G, locationsEvent[0], locationsParc[indexLocationsParc], new ArrayList<String>(Arrays.asList(locationsParking)), "Graphe - Évènement, parkings et parc", "Graphe - Évènement, parkings et parc");
//		long end = System.nanoTime();
//		System.out.println("Temps d'exécution:"+(end-start)+" ns.");
	}
	
	
}
